package com.parent.framework.config;

import com.github.xiaoymin.knife4j.core.util.StrUtil;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author yan
 * @date 2023/9/30 4:24
 */
@Data
@NoArgsConstructor
@Configuration
public class SwaggerConfig {

    @Resource
    private Environment environment;
    private final static String authorization = HttpHeaders.AUTHORIZATION;
    private final static String apiGroupName = "api";
    private final static String jwtGroupName = "jwt";
    private final static String otherGroupName = "other";
    private final static String GroupSuffix = "_Group";

    public String getAuthorization() {
        String property = environment.getProperty("api.authorization");
        String result = property != null && !"".equals(property) ? property : authorization;
        return result;
    }


    public String getApiVersion() {
        String apiVersion = environment.getProperty("api.common.version");
        return apiVersion;
    }

    public String getApiTitle() {
        String apiTitle = environment.getProperty("api.common.title");

        return apiTitle;
    }

    public String getApiDescription() {
        String apiDescription = environment.getProperty("api.common.description");

        return apiDescription;
    }

    public String getApiTermsOfService() {
        String apiTermsOfService = environment.getProperty("api.common.termsOfService");

        return apiTermsOfService;
    }

    public String getApiLicense() {
        String apiLicense = environment.getProperty("api.common.license");
        return apiLicense;
    }

    public String getApiLicenseUrl() {
        String apiLicenseUrl = environment.getProperty("api.common.licenseUrl");
        return apiLicenseUrl;
    }

    public String getApiExternalDocDesc() {
        String apiExternalDocDesc = environment.getProperty("api.common.externalDocDesc");

        return apiExternalDocDesc;
    }

    public String getApiExternalDocUrl() {
        String apiExternalDocUrl = environment.getProperty("api.common.externalDocUrl");
        return apiExternalDocUrl;
    }

    public String getApiContactName() {
        String apiContactName = environment.getProperty("api.common.contact.name");

        return apiContactName;
    }

    public String getApiContactUrl() {
        String apiContactUrl = environment.getProperty("api.common.contact.url");

        return apiContactUrl;
    }

    public String getApiContactEmail() {
        String apiContactEmail = environment.getProperty("api.common.contact.email");
        return apiContactEmail;
    }

    public String getServerServletContextPath() {
        String serverServletContextPath = environment.getProperty("server.servlet.context-path");
        System.err.println(serverServletContextPath);
        serverServletContextPath =
                serverServletContextPath != null && !"".equals(serverServletContextPath) ?
                        serverServletContextPath : "";
        return serverServletContextPath;
    }

    @Bean
    public OpenAPI getOpenApiDocumentation() {
        ExternalDocumentation externalDocs = new ExternalDocumentation();
        String apiExternalDocDesc = getApiExternalDocDesc();
        if (StrUtil.isNotBlank(apiExternalDocDesc)) {
            externalDocs.description(apiExternalDocDesc);
        }
        String apiExternalDocUrl = getApiExternalDocUrl();
        if (StrUtil.isNotBlank(apiExternalDocUrl)) {
            externalDocs.description(apiExternalDocUrl);
        }


        Contact contact = new Contact();
        String apiContactName = getApiContactName();
        if (StrUtil.isNotBlank(apiContactName)) {
            contact.name(apiContactName);
        }
        String apiContactUrl = getApiContactUrl();
        if (StrUtil.isNotBlank(apiContactUrl)) {
            contact.url(apiContactUrl);
        }
        String apiContactEmail = getApiContactEmail();
        if (StrUtil.isNotBlank(apiContactEmail)) {
            contact.email(apiContactEmail);
        }

        License license = new License();
        String apiLicense = getApiLicense();
        if (StrUtil.isNotBlank(apiLicense)) {
            license.name(apiLicense);
        }
        String apiLicenseUrl = getApiLicenseUrl();
        if (StrUtil.isNotBlank(apiLicenseUrl)) {
            license.url(apiLicenseUrl);
        }

        Info info = new Info();
        String apiTitle = getApiTitle();
        if (StrUtil.isNotBlank(apiTitle)) {
            info.title(apiTitle);
        }
        String apiDescription = getApiDescription();
        if (StrUtil.isNotBlank(apiDescription)) {
            info.description(apiDescription);
        }
        String apiVersion = getApiVersion();
        if (StrUtil.isNotBlank(apiVersion)) {
            info.version(apiVersion);
        }
        info.contact(contact);
        String apiTermsOfService = getApiTermsOfService();
        if (StrUtil.isNotBlank(apiTermsOfService)) {
            info.termsOfService(apiTermsOfService);
        }

        info.license(license);


        /**
         * 全局参数
         */
        // 设置 spring security jwt accessToken 认证的请求头 Authorization: Bearer xxx.xxx.xxx
        String authorization = getAuthorization();
        /*System.err.println(authorization);*/
        Components components = new Components();
//        components.addSecuritySchemes(
//                authorization, new SecurityScheme()
//                        .type(SecurityScheme.Type.HTTP)
//                        .in(SecurityScheme.In.HEADER)
//                        .bearerFormat("Bearer")
//                        .scheme("bearer")
//
//        );
        Map<String, SecurityScheme> securitySchemas = buildSecuritySchemes();
        components.securitySchemes(securitySchemas);

        SecurityRequirement securityRequirement = new SecurityRequirement();
        securityRequirement.addList(authorization);

        OpenAPI openAPI = new OpenAPI();
        openAPI.info(info);
        openAPI.components(components);

        openAPI.addSecurityItem(securityRequirement);
        openAPI.externalDocs(externalDocs);

        securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
        return openAPI;
    }

    /**
     * 配置所有组(api,jwt,other)
     *
     * @return
     */
    public static List<GroupedOpenApi> buildAllGroupedOpenApi() {
        //实现一次性配置 分组生效需要加@Bean
        // 但是yml中配置组名一致时会报错：Duplicate key org.springdoc.webmvc.api.OpenApiWebMvcResource
        //因此不建议一次性配置 除非不使用yml配置或者保证yml中的组名 不是 api,jwt,other这3种
        GroupedOpenApi api = buildApiGroupedOpenApi();
        GroupedOpenApi jwt = buildJwtGroupedOpenApi();
        GroupedOpenApi other = buildOtherGroupedOpenApi();
        List<GroupedOpenApi> openApiList = Stream.of(api, jwt, other).collect(Collectors.toList());
        return openApiList;
    }

    /**
     * 配置api组
     * 注意请和yaml中配置的组名不要重复(俩者配置同时生效)
     *
     * @return
     */

    //@Lazy//懒加载
    public static GroupedOpenApi buildApiGroupedOpenApi() {
        String path = new StringBuilder()
                .append("/api/**").toString();
        List<String> paths = Stream.of(path).collect(Collectors.toList());


        Parameter signParameter = buildHeaderParameter("sign", "验签",
                null, "验签"
                , null, null);

        Parameter timestampParameter = buildHeaderParameter("timestamp", "时间戳",
                null, System.currentTimeMillis() + 3000 + ""
                , null, "时间戳");
        //加入全局
        List<Parameter> parameterList = new ArrayList<>();
        parameterList = Stream
                .of(signParameter, timestampParameter)
                .collect(Collectors.toList());

        GroupedOpenApi groupedOpenApi = buildGroupedOpenApi(apiGroupName, paths, null, parameterList);
        return groupedOpenApi;
    }

    /**
     * 配置jwt组
     * 注意请和yaml中配置的组名不要重复(俩者配置同时生效)
     *
     * @return
     */

    //@Lazy//懒加载
    public static GroupedOpenApi buildJwtGroupedOpenApi() {
        String path = new StringBuilder()
                .append("/jwt/**").toString();
        List<String> paths = Stream.of(path).collect(Collectors.toList());
        List<Parameter> parameterList = Stream
                .of(buildSecurityHeaderParameter(authorization))
                .collect(Collectors.toList());

        GroupedOpenApi groupedOpenApi = buildGroupedOpenApi(jwtGroupName, paths, null, parameterList);
        return groupedOpenApi;
    }

    /**
     * 配置other组
     * 注意请和yaml中配置的组名不要重复(俩者配置同时生效)
     *
     * @return
     */

    //@Lazy//懒加载
    public static GroupedOpenApi buildOtherGroupedOpenApi() {
        //无需验证，无组必传参数
        String apiPath = new StringBuilder()
                .append("/api/**").toString();
        String jwtPath = new StringBuilder()
                .append("/jwt/**").toString();
        List<String> excludePaths = Stream.of(apiPath, jwtPath).collect(Collectors.toList());
        GroupedOpenApi groupedOpenApi = buildGroupedOpenApi(otherGroupName, null, excludePaths, null);
        return groupedOpenApi;
    }

    /**
     * 配置 ，组|指定路径|全局 --必传参数
     *
     * @param group         该组名
     * @param paths         该组路径
     * @param excludePaths  该组排除路径
     * @param parameterList 该组必传参数
     * @return
     */
    public static GroupedOpenApi buildGroupedOpenApi(String group, List<String> paths, List<String> excludePaths, List<Parameter> parameterList) {
        GroupedOpenApi.Builder builder = GroupedOpenApi.builder();
        if (StrUtil.isNotBlank(group)) {
            //加组配置
            builder.group(group);
        }
        if (paths != null && !paths.isEmpty()) {
            //组路径
            String[] pathsToMatch = paths.toArray(new String[paths.size()]);
            builder.pathsToMatch(pathsToMatch);
        }
        if (excludePaths != null && !excludePaths.isEmpty()) {
            //组排除路径
            String[] pathsToExclude = excludePaths.toArray(new String[excludePaths.size()]);
            builder.pathsToExclude(pathsToExclude);
        }
        if (parameterList != null && !parameterList.isEmpty()) {
            builder.addOperationCustomizer(
                    //加全局变量
                    (operation, handlerMethod) -> {
                        Operation reOperation = new Operation();
                        for (Parameter parameter : parameterList) {
                            //再原有方法参数基础上添加全局参数
                            reOperation = operation.addParametersItem(parameter);
                        }
                        //该方法会覆盖原有参数
                        //Operation parameters = operation.parameters(parameterList);
                        return reOperation;
                    }
            );
        }
        return builder.build();
    }

    /**
     * 安全模式，这里配置通过请求头 Authorization 传递 token 参数
     */
    private Map<String, SecurityScheme> buildSecuritySchemes() {
        String authorization = getAuthorization();
        Map<String, SecurityScheme> securitySchemes = new HashMap<>();
        SecurityScheme securityScheme = new SecurityScheme()
                .type(SecurityScheme.Type.APIKEY) // 类型
                .name(authorization) // 请求头的 name
                .in(SecurityScheme.In.HEADER); // token 所在位置
        securitySchemes.put(authorization, securityScheme);
        return securitySchemes;
    }


    /**
     * 构建 Authorization 认证请求头参数
     * <p>
     * 解决 Knife4j <a href="https://gitee.com/xiaoym/knife4j/issues/I69QBU">Authorize 未生效，请求header里未包含参数</a>
     *
     * @return 认证参数
     */
    private static Parameter buildSecurityHeaderParameter(String authorization) {
        //String authorization = getAuthorization();
        Parameter parameter = new Parameter();
        parameter.name(authorization); // header 名
        parameter.description("认证 Token");// 描述
        parameter.in(String.valueOf(SecurityScheme.In.HEADER));// 请求 header
        parameter.schema(new StringSchema()
//                ._default("Bearer ") 最好关闭
                .name(authorization)
                .description("认证 Token"));//
        return parameter;
    }

    /**
     * 自定义参数
     *
     * @param parameterName        名称
     * @param parameterDescription 描述
     * @param parameterIn
     * @param schemaDefault
     * @param schemaName
     * @param schemaDescription
     * @return
     */
    private static Parameter buildHeaderParameter
    (@NotNull String parameterName,
     @NotNull String parameterDescription,
     String parameterIn,
     String schemaDefault,
     String schemaName,
     String schemaDescription
    ) {
        // header 名
        parameterName = StrUtil.isNotBlank(parameterName) ? parameterName : "header&" + System.currentTimeMillis();
        // 描述
        parameterDescription = StrUtil.isNotBlank(parameterDescription) ? parameterDescription : "认证 Token";
        // 请求 header
        parameterIn = StrUtil.isNotBlank(parameterIn) ? parameterIn : String.valueOf(SecurityScheme.In.HEADER);

        schemaDefault = StrUtil.isNotBlank(schemaDefault) ? schemaDefault : "StringSchema";
        schemaDescription = StrUtil.isNotBlank(schemaDescription) ? schemaDescription : "schemaDescription";


        Parameter parameter = new Parameter();
        parameter.name(parameterName);
        parameter.description(parameterDescription);
        parameter.in(parameterIn);

        StringSchema stringSchema = new StringSchema();
        stringSchema._default(schemaDefault);

        Schema schema = stringSchema.name(schemaName);
        schema.description(schemaDescription);

        parameter.schema(schema);// 默认：使用用户编号为 1
        return parameter;
    }
}
